/**
	Pulsar engine. Core.
	Cross platform threads

	Copyright (C) 2013-2014 Dmitry Dolgov

	This program is free software; you can redistribute it and/or modify it under the terms of the
	GNU General Public License as published by the Free Software Foundation; either version 2 of
	the License, or (at your option) any later version.

	This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
	without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
	See the GNU General Public License for more details.
*/

#include <pulsar/pulsar.h>
#include <pulsar/src/pulsar_intr.h>

namespace pl
{

/// Default task queue size
static const pl_uint32 _DEFAULT_QUEUED_TASKS = 5;

/// List of allowed function arguments
enum FunctionType
{
	/// Special kind of function (exit from thread)
	_FUNCTION_TYPE_EXIT,
	/// f()
	_FUNCTION_TYPE_0,
	/// f(int)
	_FUNCTION_TYPE_1INT,
	/// f(void*)
	_FUNCTION_TYPE_1PTR,
	/// f(int, int)
	_FUNCTION_TYPE_2INT,
	/// f(void*, int)
	_FUNCTION_TYPE_2PTRINT,
};

/**
	Internal job declaration
*/
struct _Job
{
	_Job() {}
	explicit _Job(FunctionType _type) :
	type(_type)
	{
		return;
	}

	FunctionType type;

	pl::Function<void()> cb0;
	pl::Function<void(pl_int64)> cb1Int;
	pl::Function<void(void*)> cb1Ptr;
	pl::Function<void(pl_int64, pl_int64)> cb2Int;
	pl::Function<void(void*, pl_int64)> cb2PtrInt;

	pl_int64 arg1;
	pl_int64 arg2;
	void* ptr;
};

} // namespace pl

/**
*/
pl::Thread::Thread(int queueSize /*= 0*/, int stackSize /*= 0*/, const pl_char* name /*=NULL*/) :
_queueSize(sizeof(_Job)*(queueSize <= 0 ? _DEFAULT_QUEUED_TASKS : queueSize)),
_tasksQueue(sizeof(_Job) * (_queueSize + 1), pl::QUEUE_PUSH_MODE_FAIL),
_semaQueue(0, _queueSize+1),
_semaExit(0, 1),
_threadHandler(0)
{
	PL_ASSERTE(queueSize >= 0, PL_T("Wrong arguments"));

	_name.setSafe(name);
	_threadHandler = ThreadHandler::_createThread(this, stackSize, _name.get());
	if (_threadHandler == NULL)
	{
		plLogError(PL_LOG_NAME, PL_T("Can't create new thread"));
	}
	else
	{
		pl::IAppDebug::callOnThread(pl::THREAD_EVENT_START, _name.get());
	}
	return;
}

/*
	Thread dtor.
*/
pl::Thread::~Thread()
{
	if (_threadHandler == NULL)
	{
		// Invalid thread
		return;
	}

	if (!wait(0))
	{
		IAppDebug::callOnThread(THREAD_EVENT_CANT_DTOR, _name.get());
	}
	if (!wait(pl::WAIT_INFINITE))
	{
		if (_threadHandler->isThreadRunning())
		{
			_Job job(_FUNCTION_TYPE_EXIT);
			_tasksQueue.push(&job, sizeof(job));
			_semaQueue.release();
			_semaQueue.release();
			_semaExit.wait(-1);
		}
		else
		{
			plLogFatal(PL_LOG_NAME, PL_T("Abnormal thread termination"));
		}
	}
	else
	{
		plLogFatal(PL_LOG_NAME, PL_T("Can't terminate thread"));
		_semaExit.release();
	}
	IAppDebug::callOnThread(pl::THREAD_EVENT_END, _name.get());
	PL_DELETE _threadHandler;
	return;
}

/**
*/
bool pl::Thread::wait(pl_int32 timeoutMs)
{
	if (_threadHandler == NULL || !_threadHandler->isThreadRunning())
	{
		return false;
	}

	pl_uint32 timeEntry = pl::getTimeMS();

	PL_ASSERTW(timeoutMs == pl::WAIT_INFINITE || timeoutMs >= 0, PL_T("Wrong arguments"));

	if (!_semaQueue.wait(0))
	{
		// No tasks in queue
		return false;
	}
	_semaQueue.release();
	timeEntry += timeoutMs; // timeEntry is a time to exist

	while (true)
	{
		if (timeoutMs != pl::WAIT_INFINITE && pl::getTimeMS() > timeEntry)
		{
			break;
		}

		pl::sleepMs(1);

		if (!_semaQueue.wait(0))
		{
			// No tasks in queue
			return false;
		}
		_semaQueue.release();
	}
	return true;
}

/*
*/
bool pl::Thread::setProcessor(int index)
{
	if (_threadHandler == NULL)
	{
		return false;
	}
	return _threadHandler->setThreadProcessor(index);
}

/*
*/
bool pl::Thread::setPriority(ThreadPrior prior, int order)
{
	if (_threadHandler == NULL)
	{
		return false;
	}
	return _threadHandler->setThreadPriority(prior, order);
}

/*
*/
bool pl::Thread::queueTask(const pl::Function<void()>& cb)
{
	_Job job(_FUNCTION_TYPE_0);
	job.cb0 = cb;
	return _queueTask(job);
}

bool pl::Thread::queueTask(const pl::Function<void(pl_int64)>& cb, int value)
{
	_Job job(_FUNCTION_TYPE_1INT);
	job.cb1Int = cb;
	job.arg1 = value;
	return _queueTask(job);
}

bool pl::Thread::queueTask(const pl::Function<void(void*)>& cb, void* ptr)
{
	_Job job(_FUNCTION_TYPE_1PTR);
	job.cb1Ptr = cb;
	job.ptr = ptr;
	return _queueTask(job);
}

bool pl::Thread::queueTask(const pl::Function<void(pl_int64, pl_int64)>& cb, pl_int64 arg1, pl_int64 arg2)
{
	_Job job(_FUNCTION_TYPE_2INT);
	job.cb2Int = cb;
	job.arg1 = arg1;
	job.arg2 = arg2;
	return _queueTask(job);
}

bool pl::Thread::queueTask(const pl::Function<void(void*, pl_int64)>& cb, void* ptr, pl_int64 arg1)
{
	_Job job(_FUNCTION_TYPE_2PTRINT);
	job.cb2PtrInt  = cb;
	job.ptr = ptr;
	job.arg1 = arg1;
	return _queueTask(job);
}

bool pl::Thread::_queueTask(const _Job& job)
{
	if (_threadHandler == NULL || !_threadHandler->isThreadValid())
	{
		// Thread is invalid
		return false;
	}

	int count = _tasksQueue.count();
	bool rc = _tasksQueue.push(&job, sizeof(job));
	if (rc)
	{
		_semaQueue.release();
		if (count == 0)
		{
			// Release sema one more time to have 1 additional pass throw thread loop
			_semaQueue.release();
		}
	}
	return rc;
}

/**
*/
void pl::Thread::_threadProc()
{
	while (_semaQueue.wait(-1))
	{
		_Job job;
		int bufSize = sizeof(_Job);

		if (_tasksQueue.pop(&job, bufSize))
		{
			if (job.type == _FUNCTION_TYPE_EXIT)
			{
				break;
			}
			IAppDebug::callOnThread(THREAD_EVENT_START_JOB, _name.get());

			switch (job.type)
			{
				case _FUNCTION_TYPE_0:
					(job.cb0)();
					break;
				case _FUNCTION_TYPE_1INT:
					(job.cb1Int)(job.arg1);
					break;
				case _FUNCTION_TYPE_1PTR:
					(job.cb1Ptr)(job.ptr);
					break;
				case _FUNCTION_TYPE_2INT:
					(job.cb2Int)(job.arg1, job.arg2);
					break;
				case _FUNCTION_TYPE_2PTRINT:
					(job.cb2PtrInt)(job.ptr, job.arg1);
					break;
				case _FUNCTION_TYPE_EXIT:
				// Already processed above
				default:
					PL_ASSERTE(FALSE, PL_T("Internal error"));
			}
			IAppDebug::callOnThread(THREAD_EVENT_END_JOB, _name.get());
		}
	}
	_semaExit.release();
	return;
}

//
// End of file 'pulsar_thread.cpp'
//
